﻿using HQHS.Library.Common.Colloection;
using HQHS.Library.Common.Threading;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace HQHS.Library.Common.Caching
{
    public class TimestampCacheManagerAsync<TKey, TValue> : ICacheManager<TKey, TValue>, IDisposable
    {
        private SynchronizedDictionary<TKey, CachedItemInfo<TKey, TValue>> _cache;
        private bool _isDisposed;
        private bool _isRefreshing;
        private KeyedReaderWriterLockSlim<TKey> _loadLock;
        private LoadItemDelegate _loadMethod;
        private TimeSpan _stopRefreshTimeout;
        private TimeSpan _timeout;
        private Timer _timer;
        public LoadItemDelegate LoadItem
        {
            get
            {
                return _loadMethod;
            }
        }

        static TimestampCacheManagerAsync()
        {
        }

        public TimestampCacheManagerAsync(SynchronizedDictionary<TKey, CachedItemInfo<TKey, TValue>> list, KeyedReaderWriterLockSlim<TKey> loadLock, LoadItemDelegate loadMethod)
        {
            this._isDisposed = false;
            this._cache = list;
            this._loadLock = loadLock;
            this._loadMethod = loadMethod;
            TimeSpan defaultTimeout = TimeSpan.MinValue;
            this.SetTimeout(defaultTimeout);
        }

        public TimestampCacheManagerAsync(SynchronizedDictionary<TKey, CachedItemInfo<TKey, TValue>> list, KeyedReaderWriterLockSlim<TKey> loadLock, LoadItemDelegate loadMethod, TimeSpan timeout)
        {
            this._isDisposed = false;
            this._cache = list;
            this._loadLock = loadLock;
            this._loadMethod = loadMethod;
            this.SetTimeout(timeout);
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if ((!this._isDisposed && disposing) && this._timer != null)
            {
                this._timer.Dispose();
            }
            this._isDisposed = true;
        }

        protected virtual void LoadElement(CachedItemInfo<TKey, TValue> element, KeyedReaderWriterLockSlim<TKey> loadLock, LoadItemDelegate loadMethod)
        {
            loadLock.EnterWriteLock(element.Key);
            try
            {
                element.LastRefreshTimestamp = DateTime.Now;
                TValue local = (TValue)loadMethod(element.Key);
                element.Value = local;
                element.IsLoaded = true;
            }
            finally
            {
                loadLock.ExitWriteLock(element.Key);
            }
        }

        public void Refresh(ref CachedItemInfo<TKey, TValue> element, LoadItemDelegate loadMethod, KeyedReaderWriterLockSlim<TKey> loadLock)
        {
            if (this.ShouldRefreshInline(element))
            {
                loadLock.EnterWriteLock(element.Key);
                try
                {
                    if (this.ShouldRefreshInline(element))
                    {
                        this.LoadElement(element, loadLock, loadMethod);
                    }
                }
                finally
                {
                    loadLock.ExitWriteLock(element.Key);
                }
            }
        }

        public void RefreshAll()
        {
            this.RefreshTimer_Tick(true);
        }

        private void RefreshTimer_Tick(object state)
        {
            bool flag = false;
            try
            {
                if (state is bool)
                {
                    flag = (bool)state;
                }
                lock (this)
                {
                    if (this._isRefreshing)
                    {
                        return;
                    }
                    this._isRefreshing = true;
                }
                try
                {
                    this.ReloadAllElements(flag);
                }
                finally
                {
                    this._isRefreshing = false;
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private void ReloadAllElements(bool throwException)
        {
            foreach (CachedItemInfo<TKey, TValue> info in this._cache.GetValueArray())
            {
                if (DateTime.Compare(info.LastRequestedTimestamp, DateTime.Now.Subtract(this._stopRefreshTimeout)) > 0)
                {
                    try
                    {
                        this.LoadElement(info, this._loadLock, this._loadMethod);
                    }
                    catch
                    {
                    }
                }
            }
        }

        public void SetStopRefreshTimeout(TimeSpan stopRefreshTimeout)
        {
            if (stopRefreshTimeout > this.Timeout)
            {
                this._stopRefreshTimeout = stopRefreshTimeout;
            }
        }

        public void SetTimeout(TimeSpan timeout)
        {
            this.SetTimeout(timeout, true);
        }

        public void SetTimeout(TimeSpan timeout, bool setStopRefreshTimeout)
        {
            if (timeout < TimeSpan.Zero)
            {
                throw new ArgumentOutOfRangeException("timeout", "Timeout must be greater than or equal to TimeSpan.Zero.");
            }
            this._timeout = timeout;
            if (setStopRefreshTimeout)
            {
                this._stopRefreshTimeout = this._timeout.Add(this._timeout);
            }
            if (timeout == TimeSpan.Zero)
            {
                if (this._timer != null)
                {
                    this._timer.Dispose();
                    this._timer = null;
                }
            }
            else if (this._timer == null)
            {
                this._timer = new Timer(new TimerCallback(this.RefreshTimer_Tick), null, this._timeout, this._timeout);
            }
            else
            {
                this._timer.Change(this._timeout, this._timeout);
            }
        }

        private bool ShouldRefreshInline(CachedItemInfo<TKey, TValue> element)
        {
            return (!element.IsLoaded || ((DateTime.Compare(element.LastRequestedTimestamp, DateTime.Now.Subtract(this._stopRefreshTimeout)) < 0) || (this._timeout == TimeSpan.Zero)));
        }

        public TimeSpan StopRefreshTimeout
        {
            get
            {
                return this._stopRefreshTimeout;
            }
        }

        public TimeSpan Timeout
        {
            get
            {
                return this._timeout;
            }
        }
    }
}
